package com.googlecode.lightest.studio

import org.codehaus.groovy.control.CompilationFailedException
import com.googlecode.lightest.core.LightestTestCase
import com.googlecode.lightest.studio.Annotation
import com.googlecode.lightest.studio.TestReaderException
import com.googlecode.lightest.studio.Test

/**
 * Creates Test domain objects based on reading Groovy test files.
 */
class TestReaderService {
    /** the annotation class that identifies a test method */
    public static final Class TEST =
        Class.forName('org.testng.annotations.Test')

    boolean transactional = true

    Set<Test> readDir(File testDir, GroovyClassLoader classLoader) {
        def tests = new HashSet<Test>()

        if (! testDir.isDirectory()) {
            throw new TestReaderException('Not a directory: ${testDir}')
        }

        if (testDir.exists()) {
            testDir.eachFile { file ->
                if (file.name.endsWith('.groovy')) {
                    try {
                        tests.addAll(readFile(file, classLoader))
                    }
                    catch (TestReaderException tre) {
                        log.debug("Couldn't read ${file}: ${tre}")
                    }
                }
            }
        }

        return tests
    }

    /**
     * Reads a LightestTestCase class file, and returns a list of Test domain
     * objects, one for each @Test annotated method contained in the file.
     * Throws an exception if the operation fails for any reason.
     *
     * @param testFile     the test file to analyse and return as a Test domain
     *                     object
     * @param classLoader  the class loader to use to load the file
     *
     * @throws TestReaderException
     */
    Set<Test> readFile(File testFile, GroovyClassLoader classLoader) {
        assert classLoader != null

        def tests = new HashSet<Test>()
        def testClass

        try {
            testClass = classLoader.parseClass(testFile)
        }
        catch (CompilationFailedException cfe) {
            throw new TestReaderException('Failed to load class', cfe)
        }
        
        if (! LightestTestCase.class.isAssignableFrom(testClass)) {
            throw new TestReaderException('Not a LightestTestCase')
        }

        for (method in testClass.methods) {
            def annotations = method.declaredAnnotations

            if (! annotations.find { isTest(it) }) {
                continue
            }

            def test = new Test(name: method.name, className: testClass.name)
            addAnnotations(test, annotations)
            tests << test
        }

        return tests
    }

    protected boolean isTest(a) {
        return TEST.isAssignableFrom(a.annotationType())
    }

    private void addAnnotations(Test test, annotations) {
        for (annotation in annotations) {
            def type = annotation.annotationType()
            def accessors = type.declaredMethods.findAll
                { it.name != 'toString' } .collect
                { it.name }
            def members = [:]

            for (accessor in accessors) {
                def value = annotation."${accessor}"()

                if (value == null) {
                    continue
                }
                if (! (value instanceof String) &&
                    ! (value instanceof String[])) {
                    continue
                }
                if (value instanceof String && value == "") {
                    continue
                }
                if (value instanceof String[] && value.length == 0) {
                    continue
                }

                if (isTest(annotation) && accessor == 'description') {
                    test.description = value
                }
                else {
                    members[accessor] = value
                }
            }

            if (members.size() == 0) {
                if (isTest(annotation)) {
                    continue
                }

                def a = new Annotation(name: type.name)
                test.addToAnnotations(a)
            }
            else if (members.size() == 1 && members.value) {
                def a = new Annotation(name: type.name)
                addAnnotationValues(a, members.value)
                test.addToAnnotations(a)
            }
            else {
                members.each { member, value ->
                    def a = new Annotation(name: type.name, member: member)
                    addAnnotationValues(a, value)
                    test.addToAnnotations(a)
                }
            }
        }
    }

    /**
     * We only ever add String values.
     */
    private void addAnnotationValues(Annotation a, values) {
        if (values instanceof String) {
            a.addToValues(values)
        }
        else if (values instanceof String[]) {
            for (value in values) {
                a.addToValues(value)
            }
        }
    }
}
